Выполнение ячейки
Ctrl + Enter
или
Shift + Enter
Комментирование строки
Ctrl + /
Создать ячейку выше
Обратите внимание, эта команда и команды ниже будут работать, если у вас выделена ячейка (она подсвечена голубым, при этом курсор не мелькает внутри ячейки)
a
Создать ячейку ниже
b
Объединить выделенные ячейки
Shift + m
Вырезать ячейки
x
Копировать ячейки
c
Вставить скопированные/вырезанные ячейки
v
Самые важные – числа и строки
Числа записываются как почти везде – просто последовательность цифр:
0, -5, 100
Дробные числа представляются как десятичные дроби, дробная часть обособляется точкой:
10.5, 0.0, -300.5034
Строки заключены в кавычки – 'Ivan'. Кавычки могут быть одинарными, двойными, или их утроенной версией:
'', "", '''''', """"""
variable = 3
Слева – название переменной, затем следует знак равно, а справа – значение, которое было присвоено переменной.
Нужны для хранения значений, при вычислении вместо переменной подставляется её значение.
Числа можно сравнивать, в результате сравнений получается логическое значение – True или False
Строки можно складывать, получая их соединение (конкатенат)
'Vasya' + 'Petrov'
'VasyaPetrov'
Более сложный тип данных, позволяющий хранить много значений различных типов.
Для его создания необходимо перечислить внутри квадратных скобок [] значения через запятую:
employees = ['Anatoly', 'Alexander', 'Lavrentii']
employees
Список можно изменять (например, добавлять новые значения):
employees.append('Rostislav')
employees
В данном случае мы использовали метод .append(), добавляющий значение в список, у которого мы задействовали метод
Методы похожи на функции, только вызываются у конкретного объекта (в данном случае списка).
Значения (элементы) из списка можно достать с помощью индексирования – для этого укажите после списка в квадратных скобках номер нужного элемента, начиная с 0. Помимо этого можно индексироваться с конца списка, используя отрицательные числа:
employees[0]
employees[2]
employees[-1] # negative to index from the end
Также можно брать срезы (слайсы) списка. То есть получать кусочек исходного списка:
employees[1:]
employees[:2]
employees[::2]
Синтаксис среза такой
[start : stop : step]
start – от какого элемента берём значения, по умолчанию равно 0stop – до какого элемента берём значения, не включая его, по умолчанию равно длине спискаstep – шаг, с которым берём элементы, по умолчанию равен 1 Дефолтные аргументы можно пропускать
Чтобы убрать элемент из списка по его порядковому номеру (индексу) используется метод .pop()
employees.pop(1)
При этом убираемый из списка элемент возвращается пользователю (мы можем его использовать):
lecturer_name = employees.pop(0)
employees
lecturer_name
Больше информации и еще источник
Конвенциональны и не влияют на работу программы. Их соблюдают для единого стиля программ от разных программистов. Часть из них:
+ или *) разделяются пробеламиИспользуются для объяснения того, что происходит в коде. Очень важны, так как код чаще читают, чем пишут, и комментарии упрощают понимание происходящего.
Начинаются с решётки, дальнейшая строка игнорируется питоном
# эта строка нужна для объяснения, она не влияет на скрипт
len() – находит длину коллекции (это группа типов данных, куда входят строки, списки, словари)len([10, 20, 35])
3
print() – печатает то, что указано внутри скобокstr() – превращает переданное значение в строкуstr(5)
'5'
int() – превращает переданное значение в целое числоint('10')
10
float() – превращает переданное значение в дробное числоfloat('2.5')
2.5
Конструкция для управления ходом программы (что делать в каких-то случаях).
if predicate:
what to do
if – ключевое слово, которое даёт понять питону что дальше будет условие (ветвь)
predicate – какое-то выражение, которое сводится к логическому (True/False)
: – элемент синтаксиса (просто так нужно, чтобы питон понял)
отступ – 4 пробела или tab (используйте в программе что-то одно из этих вариантов), используется для обозначения на каком уровне мы находимся, улучшает читаемость
what to do – команды, которые вы хотите выполнить, если попали в эту ветку (то есть predicate True); все строчки в одном условии должны быть с этим уровнем отступа, чтобы выполняться в нём
Помимо простого условия, которое или выполнится или нет, есть более сложные – с 2-мя и более ветвями. Для их обозначения используются ключевые слова elif и else. Их можно использовать только если условие началось (вплотную до этого была ветка if или elif)
elif – ключевое слово, означающее, что дальше идёт условие, которое будет выполняться только если все предыдущие условия не выполнились (predicate в них был False) и предикат этой ветки True
Для использования elif обязательно должна быть ветка if выше
else - ключевое слово, означающее, что дальше идёт условие, которое будет выполняться в том случае, если все предыдущие условия не выполнились
Для использования else обязательно должна быть ветка if или elif выше
if 10 > 0:
print('1st branch was executed')
elif 10 < 0:
print('This branch is False')
else:
print('This branch is unreachable in this program')
Для логических значений True и False есть свои специальные операторы (как + или * для чисел). Они бывают полезны при задании предикатов в условиях и для анализа таблиц, где есть логический тип. Например, таблица с клиентами, где есть колонка просрочил ли клиент выплаты по кредитам.
Итак, что же это за операторы?
not – инвертирует логическое значениеnot True
not False
or – даёт True, если хотя бы один из операндов TrueFalse or True
False or False
and – даёт False, если хотя бы один из операндов FalseFalse and True
True and True
Логические действия можно соединять друг с другом и указывать порядок выполнения операций, прямо как с арифметическими действиями!
True and (False or True) # True and True
Конструкция для перебора значений из списка
for something in collection:
what to do
for – ключевое слово, которое даёт понять питону, что дальше будет цикл
something – переменная, куда будет последовательно подставляться значение из списка; нужна, чтобы обращаться к значению
in – ключевое слово, показывающее, что мы работаем с элементом коллекции
: – как в условии
отступ – как в условии
what to do – тело цикла; то, что будет происходить для каждого из элементов
for x in [1, 2, 3]:
print(x + 10)
Циклы и условия можно комбинировать и получать более сложные программы. Отступы отображают питону и программисту какая строка к какой конструкции относится.
# Create list with some data
rates = [1.3, 0.95, 1.2, 1.1, 0.7, 1.35]
# Here comes the cycle
for rate in rates:
if rate > 1: # Start condition, 1 indent for cycle, 1 indent for condition
print('rate is greater than 1') # body of condition, 1 indent for cycle, 1 indent for condition
else:
print('rate is lesser than 1')
Очень полезно иметь матрицу (template) строки, в которую можно подставлять произвольную подстроку.
intro = "My name is {}, I'm from {}"
intro.format('Sasha', 'Russia')
intro.format('Katya', 'Russia')
{} внутри строки означают возможность применения метода format. Переданные в него аргументы будут подставлены в соответствующие скобки.
Существуют и другие варианты темплэйтов
Больше информации
Ассоциативный тип данных – в нём каждый элемент является парой ключ-значение. Для создания нужно указать элементы внутри фигурных скобок – {}.
Синтаксис элементов в словаре – {key: value}.
salaries = {'Ivan': 30000} # key 'Ivan' is associated with value 30000
Чтобы узнать что ассоциировано с ключом 'Ivan', нужно проиндексироваться по нему:
salaries['Ivan']
Ключами словаря могут быть строки и числа, а значениями почти что угодно – числа, строки, списки, даже другие словари!
salaries['Anna'] = 50000
salaries
for name in salaries: # salaries.keys() is analogous
print(name)
for salary in salaries.values():
print(salary)
Библиотека – набор кода, посвящённый определённой цели (математические функции/работа в интернете/обработка текстов). Используется, чтобы облегчить работу, связанную с этой целью. В нашем курсе самый простой пример – библиотека pandas, использующаяся для анализа данных.
Если есть библиотека, рещающая ваша задачу, то почти всегда лучше воспользоваться ей, нежели решать всё смостоятельно с нуля.
По умолчанию содержимое библиотек недоступно при работе в python. Чтобы их использовать, нужно произвести импорт:
import pandas as pd
import – ключевое слово, дающее питону понять, что будут импортировать библиотеку
pandas – название библиотеки
as – ключевое слово, означающее, что дальше будет использоваться другое название (элиас, alias)
pd – то, какое название мы решили использовать в коде вместо названия этой библиотеки; они произвольны и обычно конвенциональны (в дальнейшем будем использовать название pd)
Более простой вариант импорта без изменения названия
import module
module – название нужного вам модуля, далее в коде используется оно.
Существуют и другие способы импорта, но пока что остановимся на этих.
Библиотека для работы с данными. Без pandas анализ табличных данных в питоне совсем не то.
В следующих стэпах поговорим о его возможностяx!
pd.read_csv('path_to_your.csv') # read_excel for reading excel files
Считывает csv файл, который лежит по указанному в скобках пути. На windows пути к файлам содержат \, который является специальным символом в строках во многих языках программирования, включая питон. Поэтому необходимо сделать следующее - либо удвоить все \ в строке с путём, либо поставить r перед строкой.
путь на windows - C:\user\docs\Letter.txt
cтрока с путём - 'C:\user\docs\Letter.txt'
валидная строка с путём - 'C:\\user\\docs\\Letter.txt' или r'C:\user\docs\Letter.txt
На серевере мы работаем с юниксовыми путями, например /home/user/letter.txt. С ними не возникает таких проблем – достаточно поместить путь в кавычки, чтобы всё было хорошо.
Аргументы (или параметры) – это настройки, которые мы можем задать в функции:
encoding – параметр в read_csv, отвечает за кодировку текста, которая может быть различной. Самая распространённая – utf-8. Пример указания кодировки:pd.read_csv('path_to_your.csv', encoding='Windows-1251') # now you are reading file encoded with Windows-1251
sep – разделитель между ячейками в строке (по умолчанию ,)pd.read_csv('path_to_your.csv', encoding='Windows-1251', sep=';') # now you additionally specified that fields are separated with ;
parse_dates – стоит ли воспринимать даты как даты? (по умолчанию воспринимаются пандасом как строки)True – пытается перевести в дату первую колонку# And create_data, payment_data columns will be treated as data
pd.read_csv('path_to_your.csv', encoding='Windows-1251', sep=';', parse_dates=['create_data', 'payment_data'])
import pandas as pd
# Paste here appropriate link from your course
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
df.head()
shape – атрибут, хранящий данные о размерах таблицы. Возвращает кортеж (для простоты – смотрите на него как на неизменяемый список). В случае датафрэйма кортеж содержит 2 значения – число строк и число колонок в нём.
df.shape
Например, в данном случае размер таблицы: 29101 строк и 14 колонки
Чтобы узнать типы колонок в вашем датафрэйме, воспользуйтесь атрибутом dtypes – он возвращает серию с описанием типа каждой колонки. Типы более-менее совпадают с типами в python, кроме нескольких различий:
Информация о типе важна для дальнейшей работы с датафрэймом (например, чтобы не произвести сложение строк, думая, что это числа).
describe – удобный метод для вывода описания числовых колонок в датафрэйме
df.describe()
describe выводит информацию о числе строк, среднем, стандартном отклонении, минимуме, максимуме и значениях по 25-ому, 50-ому и 75-ому квартилям. Он действует только на числах, так как большинство этих параметров неочевидно определяются для других типов данных (например, строк)
В идеале названия колонок осмыслены, актуальны, не содержат пробелов и на английском. Конечно, для каких-то задач, они могут быть и с пробелами, и на другом языке. В любом случае, если вы хотите их переименовать есть метод rename. Пример переименования колонки x в name, а колонки y в salary:
# Rename columns
df = df.rename(columns={'x': 'name', 'y': 'salary'})
А это пример переименовывания лэйблов строк из 0 в Ivanov и из 1 в Vasilev:
# Rename index (row names)
df = df.rename(index={0: 'Ivanov', 1: 'Vasilev'})
Один из способов переименования – передать словарь, где ключи это старые названия, а значения – новые.
В пандасе существует много способов обратиться к колонке датафрэйма. Самый удобный:
df.column_name
df – датафрэйм
column_name – название колонки
Чтобы это работало, название колонки должно состоять из одного компонента (например, слова), и не должно совпадать с названием методов датафрэйма (имя колонки count не сработает). Для языковой однородности – ещё и на английском, но это не является обязательным.
Что делать, если название колонки состоит из 2-х слов? Либо переименовать, либо использовать другой способ доступа:
df['column name']
Работает для всех случаев кроме тех, когда в названии присутствуют одинарные кавычки. Тогда либо используйте вокруг названия двойные, либо поставьте \ перед кавычками внутри. Лучше называть колонки без кавычек.
Для получения нескольких колонок передайте внутрь квадратных скобок список с именами желаемых колонок:
df[['column1', 'column2', 'column3']]
Так же просто как задавание нового значения в словаре :)
# Create new column in the df with name new_column which is equal to 5 in each cell
df['new_column'] = 5
Существует набор методов, доступных для колонок датафрэймов. Например, есть колонка money в датафрэйме, содержащая полученные объёмы денег. Применив метод sum, можно посчитать их сумму.
df.pickups.sum()
Ещё несколько примеров:
product – перемножение
std – среднеквадратичное отклонение
var – дисперсия
Приём для объединения нескольких действий в одно. Большинство методов датафрэймов возвращают вам результат, который довольно часто тоже является датафрэймом. Следовательно, от него тоже можно вызвать метод.
Ванильная запись:
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
df = df.query('pickups >= 1000')
df = df.groupby(['pickup_month', 'borough'], as_index=False).agg({'pickups': 'sum'}) # groupby is usually immediately followed by agg
df = df.sort_values(['pickups'])
df.head()
Сокращённая запись:
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
df = df.query('pickups >= 1000').groupby(['pickup_month', 'borough'], as_index=False).agg({'pickups': 'sum'}).sort_values(['pickups'])
df.head()
Как вы видите, эта запись довольно длинная, и не очень удобна для чтения. Обычно, такая цепочка оформляется в блок, где каждый метод идёт на своей строке. Есть 2 варианта оформления, какой выбрать - вопрос предпочтения и конвенций в вашей организации:
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
# \ after each nonfinal line to demarcate line continuation for python
df = df.query('pickups >= 1000') \
.groupby(['pickup_month', 'borough'], as_index=False) \
.agg({'pickups': 'sum'}) \
.sort_values(['pickups'])
df.head()
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
# Parentheses around the whole expression for the same purpose as backslash in previous example
df = (df.query('pickups >= 1000')
.groupby(['pickup_month', 'borough'], as_index=False)
.agg({'pickups': 'sum'})
.sort_values(['pickups']))
df.head()
Часто используемый приём для вычисления чего-либо по данным. Осуществляется с помощью метода groupby – группирует данные в датафрэйме по указанным колонкам:
Применение одного метода groupby не даёт видимого эффекта, хотя на самом деле все строки были объединены в группы по значению в колонке company: с одним значением в одну группу, с другим – в другую. groupby обычно используется не сам по себе, а в связке с agg или другим методом. Можно использовать несколько колонок для группировки, передав их в виде списка.
Дополнительные параметры:
as_index – принимает True или False для обозначения того, нужно ли использовать переданные для группировки колонки как индекс позднее, по умолчанию Trueagg – функция для агрегирования данных, применяется после группировки groupby'ем.
Как это работает:
агрегируем методом agg, указывая на каких колонках какие действия производить
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
df.groupby('borough').agg({'pickups':'sum'})
Существуют разные способы передать в agg что и как вы хотите агрегировать. Самый простой и полный – использовать словарь, где ключи это названия колонок, а значения – применяемые к ним функции. Чтобы применить несколько функций, используйте список функций. Функции могут быть переданы сами (sum), либо строки, их обозначающие ('sum').
В результате агрегации из массива значений (колонка) получается одно значение на каждую агрегирующую функцию.
Для такой сортировки используется метод sort_values, получающий колонку/список колонок, по которым будет идти сортировка (обратите внимание, заглавные буквы считаются меньше обычныx):
Дополнительные параметры:
ascending – принимает логическое значение, показывающее сортировать ли колонку по возрастанию
df.sort_values('borough').head()
Метод, который считает, сколько раз встречается каждое уникальное значение переменной. Например, имеется следующий набор данных:
Посчитать, сколько раз встречается каждый месяц (pickup_month), можно с помощью следующей команды:
df['pickup_month'].value_counts()
Результат возвращается в формате pd.Series (серии)
Также метод value_counts принимает на вход несколько параметров:
normalize – показать относительные частоты уникальных значений (по умолчанию равен False). dropna – не включать количество NaN (по умолчанию равен True) bins – сгруппировать количественную переменную (например, разбить возраст на возрастные группы); для использования данного параметра нужно указать, на сколько групп разбить переменную Несколько примеров:
pickup_month – в 40% наблюдений), также не удаляем из результата NaN:df['pickup_month'].value_counts(normalize=True, dropna=False)
pickups на 2 промежутка:df.pickups.value_counts(bins=2)
В пандасе есть возможность фильтровать данные используя SQL-like синтаксис. Для этого нужен метод query, принимающий строку с запросом. Внутри него можно использовать названия колонок (если они без пробелов). При использовании строк внутри запроса экранируйте кавычки \ или используйте другую пару
В query также можно передать сразу несколько условий. Условия, которые должны выполняться одновременно, соединяются с помощью and или &:
df.query('pickup_month == "Jan" and borough == "Bronx"').head()
Когда должно удовлетворяться одно из условий – or или |:
df.query('pickup_month == "Jan" or borough == "Bronx"').head()
Датафрэйм можно записать в различный формат, самый распространённый, пожалуй, csv. Для этого нужно применить к датафрэйму метод to_csv и передать в него путь, по которому вы хотите создать файл.
df.to_csv('my.csv')
Дополнительные параметры:
index – записать индекс датафрэйма в csv как первую колонку sep – используемый при записи разделитель колонок replace – применяется к строкам, принимает 2 строки: что заменить и на чтоmy_string = 'I_love_whitespaces'
my_string.replace('_',' ')
strip – применяется к строкам, по умолчанию убирает пробелы слева и справаuser = ' Vasya Fedorov '
user.strip()
round – применяется к дробным числам, округляет их, можно передать дополнительный аргумент, который означает число знаков после запятойround(3.14159265359 ,2)
Векторизация - это специальная техника, позволяющая в пандасе быстро выполнять в одну строчку операции, которые в чистом питоне требуют как минимум одного цикла. Быстрота связана с тем, что код пандаса реализован на более быстром чем питон языке, а в питоне просто представлены функции. Благодаря векторизации, мы можем делать различные операции со всеми колонками целиком, не отвлекаясь на итерирование по элементам
df.spd.head()
Умножим каждый элемент на 3
(df.spd * 3).head()
Или выясним для каждого значения больше ли оно чем 5
(df.spd > 5).head()
None это специальный тип данных в питоне, который имеет только одно одноимённое значение – None. Используется в тех случаях, когда нужно обозначить ничто. Обычно (но совсем необязательно) его возвращают функции, которые как-то изменяют данные
xs = [1, 2, 3]
a = xs.append(4)
print(xs)
print(a)
Толку от присвоения выше a = xs.append(4) нет, просто постепенно запоминайте такие функции, и не перезаписывайте ваши данные вызовами типа
xs = xs.append(4) # так делать не нужно
Служат для переиспользования кода – вы написали изумительный скрипт, который решает 20% вашей работы. Теперь вам нужно использовать его каждый день на новых данных. Чтобы это упростить, существуют функции. Они позволяют свести весь код к 1-ой строке и менять название поступающих файлов и других параметров только в 1-ом месте. Задавание функций:
def function(parameter):
# what to do
def – ключевое слово, даёт питону понять, что дальше будет задана функцияfunction – название, которое мы выбралиparameter – инпут функции, то, какие данные ей нужны для работы. Параметров может быть 0 (без инпута) или больше. Это может быть почти что угодно:what to do – тело функции, функционал, который будет работатьПо умолчанию функция при завершении своей работы ничего не вернёт обратно (в отличие от, например, метода head у датафрэйма). Чтобы она возвращала значение после вызова, добавьте ключевое слово return в теле функции перед переменной, которую хотите вернуть:
## 2 simple functions
# Without return
def print_a(a, b):
print(a)
# With return
def return_a(a, b):
return a
x = print_a(3, 5) # x is None
print(x)
y = return_a(3, 5) # y is 3
print(y)
Для работы с датой и временем можно использовать модуль datetime. Для получения данных о времени в момент вызова функции используйте функцию today в одноимённом подмодуле:
import datetime # by convention imports are placed in the head of file and separated with 2 blank lines from other code
date = datetime.datetime.today()
date
Само по себе это даст вам специальный тип даты. Чтобы перевести его в строку сделайте следующее:
strftime форматирует дату по переданному ему формату:
% – обозначает что дальше будет часть датыY – год 4-мя знакамиm – месяц 2-мя знакамиd – деньH – часM – минутыS – секундыМожно использовать только часть фрагментов даты, разделители между ними – на ваше усмотрение (в примере это / и :). Немного примеров
from datetime import datetime
# current date and time
now = datetime.now()
print(f'Full time format of now is {now}')
# Year
year = now.strftime("%Y")
print("year:", year)
# Month
month = now.strftime("%m")
print("month:", month)
# Day
day = now.strftime("%d")
print("day:", day)
# Time
time = now.strftime("%H:%M:%S")
print("time:", time)
# Date and time
date_time = now.strftime("%m/%d/%Y, %H:%M:%S")
print("date and time:",date_time)
В компьютере используется двоичная система счисления, в которой не выразить точно любое десятичное число. Из-за этого при выполнении действий может накапливаться ошибка. Обычно это не страшно (она в порядках меньше -5-ого), но бывает нужна точность. Для этого есть специальные библиотеки
unique – метод, возвращающий уникальные значения в колонке.
Уникальные значения возвращаются в форме array – о них будет сказано попозже, для простоты воспринимайте их как списки.
df.borough.unique()
nunique – метод, который считает число уникальных значений в колонке.
df.borough.nunique()
Чтобы посчитать медиану колонки, используйте метод median
df.pickups.median()
Ну а для среднего – mean
df.pickups.mean()
split – метод, разбивающий строку на куски и помещающий фрагменты в список. По умолчанию делит по пустым символам (пробел, табы, перенос строки).
brand_info = 'MARAVILLA 500 G Store_Brand'
brand_info.split()
Обычно используются, когда нужно куда-то быстро поместить нечасто используемый функционал. Если вы планируете использовать анонимную функцию больше одного раза, напишите обычную функцию :)
lambda x: do something
labmda – ключевое слово, задающее анонимную функцию (не имеющую имени)x – то, как мы назвали аргумент, принимаемый функцией: – разделяет заголовок и тело безымянной функцииdo something – тело функции, должно помещаться в одну строчку и будет автоматически возвращаться без return# Take 1 argument and add 3 to it
lambda x: x + 3
Один из примеров использования лямбда-функции – переименование колонок в датафрэйме. Здесь мы делаем их заглавными и заменяем дефисы на нижние подчёркивания
# df is a dataframe as usual
df = df.rename(columns=lambda c: c.upper().replace('-', '_'))
pd.Series – более примитивный тип данных в pandas, соответствует колонке датафрэйма. В ней хранятся данные одного типа (числа/строки/...). Работая с колонкой, мы работаем именно с серией. Часть методов и аттрибутов серии и датафрэйма совпадают.
apply – применяет переданную в него функцию ко всем колонкам вызванного датафрэйма. Чтобы применить функцию к одной колонке датафрэйма, можно выбрать её перед применением apply, например:
df.PICKUP_DT.apply(lambda time: time.split(' ')[0]).head()
df = pd.read_csv("https://stepik.org/media/attachments/bookings.csv",sep=';')
df['Reservation Status'].apply(lambda status: status.split('-')[-1]).head()
Зачастую называется джойном. Очень частая операция, которую можно сделать с помощью нескольких функций. Одна из них – merge. Обязательным аргументом является другой датафрэйм, с которым планируется объединение. Объединение идёт по общей колонке, у которой имеется одинаковый смысл и общие значения в обоих датафрэймах. Существуют различные типы джойнов, они будут рассмотрены в курсе по SQL. Самый частый, пожалуй, inner.
Здесь мы объединяем датафрэйм users_data с датафрэймом users_lovely_brand_data по колонке tc с помощью inner джойна:
user_data = pd.read_csv("https://stepik.org/media/attachments/user_data.csv")
logs = pd.read_csv("https://stepik.org/media/attachments/logs.csv")
user_data.merge(logs, how = 'inner', on = 'client').head()
В результате получается один датафрэйм, где колонки из 2-ух таблиц, относящиеся к одному наблюдению, объединяются в строку. Звучит сложно, поэтому для практики стоит попробовать сделать несколько простых джойнов :)
how – как объединять датафрэймы, одно из inner, outer, left, righton – общая колонка, по которой будет идти объединениеИндекс – это лэйбл строки в таблицы, по умолчанию является её номером. А имена колонок... это имена колонок, то есть лэйблы, по которым мы можем обращаться к каждому из столбцов.
У датафрэйма есть 2 атрибута index и columns, позволяющие получить доступ к соответствующей информации в виде array (на самом деле не совсем array)
df = pd.read_csv('https://docs.google.com/spreadsheets/d/e/2PACX-1vR-ti6Su94955DZ4Tky8EbwifpgZf_dTjpBdiVH0Ukhsq94jZdqoHuUytZsFZKfwpXEUCKRFteJRc9P/pub?gid=889004448&single=true&output=csv')
df.index
df.columns
Иногда вам может захотеться перевести индекс датафрэйма в колонку. Для этого существует метод reset_index. Индексом становится дефолтная последовательность чисел от 0 до числа строк - 1.
df.head()
df.reset_index().head()
Аргумент drop отвечает за то, нужно ли переводить индекс в колонку, или убрать его из таблицы:
df.reset_index(drop=True).head()
isna - это чудо-метод, с помощью которого можно быстро найти пропущенные значения в датафрэйме
df.head()
Применив его, на выходе мы получаем датафрэйм той же размерности, где в каждой ячейке True или False в зависимости от того было ли значение пропущено.
df.isna().head()
В связке с ним можно использовать, например, sum, чтобы посмотреть на число NA в разных колонках.
df.isna().sum()
Графики – важная часть анализа данных, так как они наглядно (если тип графика хорошо подобран) представляют данные и позволяют быстро разобраться в сути.
Чтобы в юпитер ноутбуке отображались графики выполните строчку:
%matplotlib inline
Существуют разные способы создания графиков в python, несколько популярных библиотек:
pandasseabornmatplotlibДавайте начнём постепенно в них разбираться!
Самый простой способ визуализировать данные – вызвать метод plot у датафрэйма (или его колонки). Например, гистограмма значений в колонке age:
user_data.head()
user_data.age.plot(kind='hist', bins=15)
Другой вариант записи:
user_data.age.plot.hist(bins=15)
Функции рисования имеют весьма большое количество параметров, используйте их при необходимости. bins здесь – число диапазонов (корзин/бакетов), на которые мы разделяем значения.
import seaborn as sns
# Use link from your course
df = pd.read_csv("https://stepik.org/media/attachments/bookings.csv",sep=';')
sns.distplot(df['Lead Time'])
sns.boxplot(data=df, x='Hotel', y='Lead Time')
sns.barplot(data=df, x='Hotel', y='Lead Time')
Базовая библиотека для рисования в питоне. На ней построены более продвинутые и простые в использовании типа seaborn. Через matplotlib можно нарисовать что угодно, но часто на это уходит слишком много строк кода, и её в основном используют для тонкой настройки графиков и их сохранения.
Традиционно matplotlib импортируется следующим образом:
import matplotlib.pyplot as plt
Важный момент – большинство настроек должны быть написаны к каждому графику отдельно. Иными словами, настройки, написанные в ячейке с одним графиком, не будут применены к другому.
В figure в figsize подаётся кортеж (как список, только в круглых скобках) с масштабом графика формата (ширина, высота)
import matplotlib.pyplot as plt
plt.figure(figsize=(9,6))
sns.distplot(df.query('Hotel == "City Hotel"')['Lead Time'], kde = False)
Сохранить график можно с помощью savefig , где аргумент – путь к сохраняемой картинке (желаемое название и формат):
sns.distplot(df.query('Hotel == "City Hotel"')['Lead Time'], kde = False)
plt.savefig("1.png")
startswith - строковый метод, принимающий другую строку и возвращающий True или False в зависимости от того, начинается ли исходная строка с переданной.
'Abyss'.startswith('Ab')
'Abyss'.startswith('ab')
List comprehension - часто используется как лаконичная (и убыстренная) замена циклов, где заполняется список
xs = [i + 3 for i in range(10)]
Аналогично
xs = []
for i in range(10):
xs.append(i + 3)
В comprehension'е можно прописать условия и даже вложенные циклы. Однако не стоит сильно их нагружать: читаемость кода - одно из его самых важных качеств, а вложенные comprehension'ы делают код сложнее для понимания
# Get all even numbers
evens = [i for i in range(10) if i % 2 == 0]
# Analogous to
even = []
for i in range(10):
if i % 2 == 0:
even.append(i)
Нередка ситуация, когда тип данных в колонке не соответствует желаемому (почему это вообще важно? Для разных типов определены разные операции - '0.15' * 100 не переведёт дробь в проценты).
Чтобы это исправить, есть метод astype, в который можно передать словарь, где ключи это названия колонок, а значения - новые типы для них. Метод возвращает новый датафрэйм с изменёнными типами
df = df.astype({'money': 'float'}) # df.money will be rational number after this line
Для конвертации типов колонок есть более простой вариант - передайте желаемый тип при вызове astype от колонки
df.height = df.height.astype('float') # df.height will be rational number
Чтобы убрать часть колонок из датафрэйма, воспользуйтесь методом drop, куда можно передать список из названий, которые нужно убрать. Метод также позволяет убирать строки по индексу, для указания измерения, в котором мы работаем, используется аргумент axis (0 - строки, 1 - колонки). Лучше использовать более понятные columns/index. Возвращается новый датафрэйм
df = df.drop(columns='Date') # drop Date column
df = df.drop(index=350) # drop row with 350 index
Дубликаты - повторяющиеся наблюдения, которых не должно быть. Быстро их убрать позволяет метод drop_duplicates, возвращающий таблицу без них.
df = df.drop_duplicates() # df will contain <= rows than before after this operation
df.loc[df.duplicated()]
subset - принимает список колонок, по которым нужно смотреть дупликацию
# Drop only if duplicates are in 'Date' or 'Last' columns
df.drop_duplicates(subset=['Date', 'Last'])
Строки сравниваются в лексикографическом порядке (по алфавиту, как в языковых словарях).
'1' < '2'
'abc' < 'b'
Эти 2 записи тождественны:
1 < 2 and 2 < 3
1 < 2 < 3
pd.to_datetime() - метод, позволяющий превратить строки во время. Это позволяет удобно с ним работать.
Аттрибут dt позволяет извлекать временные характеристики из колонки с датой.
df['arrival full date'] = pd.to_datetime(df['arrival full date'])
df['arrival full date'].dt.year.head()
df['arrival full date'].dt.month.head()
Также, вы можете заранее распарсить дату при загрузке датасэта, передав в parse_dates список колонок, в которых содержится дата.
pd.read_csv('some.csv', parse_dates=[1]) # order of columns starting from 0
Рассмотренный способ открывания файлов с помощью pd.read_csv - не единственный, и не первый в питоне. Традиционно любой файл открывается с помощью функции open, принимающий путь к файлу. Это менее удобно, так как является базовым способом, который далее усложняется в том же pd.read_csv
file = open('path_to_file')
У file есть различные методы на чтение содержимого, например readlines.
lines = file.readlines() # lines is a list with lines from the file
В конце работы с файлом (то есть когда он вам больше не понадобится) его нужно закрыть, для чего используется метод … close
file.close()
Существует более удобный и предпочтительный способ с контекстным мэнеджером.
Просмотр папок и многие другие операции, связанные с файлами и папками выполняются с помощью библиотеки os. Для получения списка файлов используется listdir. Метод os.listdir принимает путь к папке и возвращает её содержимое в виде списка.
import os
# Paste your home directory
os.listdir('/home/jupyter-a.ilin/shared/homeworks//python_ds_miniprojects/5_subsid/subsid')
Названия файлов в папке вместе с путём к ней, позволяют реконструировать полный путь и работать с этими файлами.
path = '/etc'
path_to_file = path + '/' + os.listdir(path)[0] # os.path.join is better for constructing path
При вложенности папок и необходимости добраться до дна можно использовать os.walk
# Paste your home directory
for path, dirs, files in os.walk('/home/jupyter-a.ilin/shared/homeworks/python_ds_miniprojects/5_subsid/subsid'):
print(files) # возвращает файлы в формате list
for path, dirs, files in os.walk('/home/jupyter-a.ilin/shared/homeworks/python_ds_miniprojects/5_subsid/'):
print(dirs) # возвращает директории в формате list (если лист пустой значит нет директорий
for path, dirs, files in os.walk('/home/jupyter-a.ilin/shared/homeworks/python_ds_miniprojects/5_subsid/'):
print(path) # возвращает пути до директории на каждой итерации цикла
На каждой итерации (1 этап цикла) метод возвращает тройку из пути к нынешней папке, списков папок и файлов, хранящихся в этой папке.
Также есть более предпочтительный вариант - модуль pathlib.
При необходимости можно выйти из цикла с помощью слова break. Это бывает нужно сделать при выполнении определённых условий.
Искусственный пример - на итерации, где в i попадёт число больше 5, цикл будет прерван, и будет выполняться код ниже него. Таким образом будут напечатаны все числа до того, которое больше 5 (7 в данном случае).
numbers = [1, 3, 2, 4, 5, 7, 10]
for i in numbers:
if i > 5:
break
print(i)
Другой частый случай - пропуск каких-то итераций, для этого используется слово continue.
Здесь будут напечатаны только чётные числа.
numbers = [1, 3, 2, 4, 5, 7, 10]
for i in numbers:
if i % 2 != 0:
continue
print(i)
Справедливости ради, простой код может быть написан без применения continue.
for i in numbers:
if i % 2 == 0:
print(i)
А он понадобится для более сложных случаев.
dropna - метод, позволяющий выкинуть из датафрэйма все строки, содержащие пропущенные значения.
data = pd.read_csv('https://stepik.org/media/attachments/taxi_peru.csv', sep=';')
data.loc[:, ['driver_id','taxi_id']].head()
Так, мы выкинем все строки, где было хотя бы одно пропущенное значение.
data.loc[:, ['driver_id','taxi_id']].dropna().head()
У dropna есть набор интересных параметров, ознакомиться с которыми можно в документации.
В питоне, чтобы узнать, есть ли элемент в списке, используется оператор in.
2 in [2,4,5]
В пандасе есть более эффективный метод isin, принимающий коллекцию, в которой содержатся искомые значения.
ads_data = pd.read_csv('~/shared/homeworks/python_ds_miniprojects/6/ads_data.csv')
wanted_clients = [34734, 112260]
ads_data.loc[ads_data.client_union_id.isin(wanted_clients)].head()
Согласно документации, работает быстрее.
wanted_clients = [34734, 112260]
ads_data.query('client_union_id == @wanted_clients').head()
Использование == как аналог isin является старым, и не работает со списками. Вместо него лучше использовать in (наконец-то они его ввели).
wanted_clients = [34734, 112260]
ads_data.query('client_union_id in @wanted_clients').head()
Для объединения 2-ух или более датафрэймов существует метод pd.concat(). Он принимает кортеж из нескольких датафрэймов, и возвращает один большой из них. Соединять можно вертикально (увеличиваем число строк) или горизонтально (увеличиваем число столбцов).
Есть менее общий вариант - метод append() принимает другой датафрэйм и прибавляет его вертикально.
Для записывания в датафрэйм новых строк из другого датафрэйма можно использовать метод append, возвращающий новый датафрэйм, где прибавлены строки из 2-ого.
both_df = first_df.append(second_df)
У функции pd.read_csv есть аргумент compression, который принимает строчку типа компрессии и открывает заархивированный файл.
# Here type of compression is zip
ads_data = pd.read_csv('ads_data.csv.zip', compression='zip')
Время может быть указано в разном формате, один из них - число секунд, прошедших с 1970 года. Кажется странным? (По-моему, да). Зато удобно - времена представляются как целые числа, которые легко вычитать, сравнивать. А при необходимости можно сконвертировать в human-readable формат.
pd.to_datetime(1554076848, unit='s')
Временные серии обладают атрибутом dt, в котором находится множество атрибутов и методов для доступа ко времени. Давайте посмотрим на часть из них.
data_time = pd.read_csv('https://docs.google.com/spreadsheets/d/e/2PACX-1vR-ti6Su94955DZ4Tky8EbwifpgZf_dTjpBdiVH0Ukhsq94jZdqoHuUytZsFZKfwpXEUCKRFteJRc9P/pub?gid=889004448&single=true&output=csv')
data_time['time'] = pd.to_datetime(data_time['time'])
data_time.time.head()
dt.microsecond - сколько микросекунд в указанном времени (то есть, если время 5 минут, 0 секунд и 3 микросекунды, то он вернёт 3, а не $5 \cdot 60 \cdot 10^6$)
data_time.time.dt.microsecond.head()
data_time.time.dt.second.head()
data_time.time.dt.minute.head()
data_time.time.dt.hour.head()
data_time.time.dt.day.head()
data_time.time.dt.weekday.head()
data_time.time.dt.day_name().head()
data_time.time.dt.week.head()
data_time.time.dt.month.head()
data_time.time.dt.month_name().head()
data_time.time.dt.year.head()
data_time.time.dt.daysinmonth.head()
Немного о timedelta - это тип данных, соответствующий разнице 2-ух времён, то есть какая-то продолжительность времени.
data = pd.read_csv('https://stepik.org/media/attachments/taxi_peru.csv',
sep=';',
parse_dates=['start_at','end_at','arrived_at'])
data['wait_time'] = (data['arrived_at'] - data['start_at'])
data.wait_time.head(10)
Все единицы измерени времени, можно извлечь сразу с помощью атрибута components.
data.wait_time.dt.components.head(6)
Метод для поиска определённых перцентилей. Принимает число от 0 до 1, обозначающее перцентиль в виде доли:
ads_data = pd.read_csv('~/shared/homeworks/python_ds_miniprojects/6/ads_data.csv')
ads_data.head()
ads_data.target_audience_count.quantile(q=0.75)
Также в q можно передать список всех желаемых перцентилей.
ads_data.target_audience_count.quantile(q=[0.5,0.7])
Что делать со значениями, не попадающими в перцентиль.
Если ровно по заданному перцентилю в датафрэйме нет значения, то по дефолту метод линейно выведет его. Поменять это поведение можно с помощью параметра interpolation. Вариант 'higher' берёт большую точку из смежных.
ads_data.target_audience_count.quantile(q=[0.5,0.7], interpolation='higher')
P.S.: Краткость - сестра таланта, но не в создании названий параметров в одну букву. Здесь сложно перепутать, так как название метода намекает, но когда будете создавать свои функции и методы, называйте всё осмысленно.
Сводные таблицы - удобный способ преобразовать данные, с возможностью применения к ним агрегирующей функции. В pandas есть 2 функции, различающиеся только тем проводится ли агрегация.
Обе принимают 3 аргумента:
index - название колонки, значения из которой станут индексамиcolumns - название колонки, значения из которой станут колонкамиvalues - название колонки, значения из которой распределяться по сформированным группамТеперь сами функции.
Преобразует датафрэйм в таблицу, где значения использованных колонок становятся новыми индексами и колонками.
ads_data = pd.read_csv('~/shared/homeworks/python_ds_miniprojects/6/ads_data.csv')
ads_data = ads_data\
.groupby(['date','platform'], as_index=False)\
.agg({'target_audience_count': 'sum'})
ads_data.head()
ads_data.pivot(index='date', columns='platform', values='target_audience_count')
Нужно принимать во внимание, что если в таблице, к которой мы хотим применить pivot нет каких то значений, например, данных по android в день 2019-04-01, то в результирующей pivot таблице мы получим NaN.
Сейчас мы специально удалим из ads_data первую строку в которой данные как раз по андроиду за 2019-04-01 и применим к ней pivot.
ads_data.iloc[1:].pivot(index='date', columns='platform', values='target_audience_count')
Всё как в предыдущем методе, только можно произвести агрегацию, получая одно значение из группы с одинаковыми значениями в новых индексах и колонках. По умолчанию берётся среднее от группы значений.
ads_data.pivot_table('target_audience_count', index='date', columns='platform')
ads_data.pivot_table('target_audience_count', index='date', columns='platform', aggfunc='max')
Колонки в датафрэйме можно также создать с помощью метода assign. Он возвращает исходный датафрэйм с добавленными колонками – нужно перезадать переменную, чтобы изменить датафрэйм.
В метод передаются аргументы формата название колонки = её содержимое – как название параметра и его значение при вызове функции. Здесь название колонок нужно писать без кавычек.
Так, мы задаём новую колонку rel, значения в которой являются отношением значений в колонке target_audience_count к значениям в колонке ad_cost:
ads_data = pd.read_csv('~/shared/homeworks/python_ds_miniprojects/6/ads_data.csv')
ads_data = ads_data.assign(rel = ads_data.target_audience_count / ads_data.ad_cost)
Очень важная вещь в некоторых сферах. Одно из простых решений - заменить все на одно значение (например, 0). Это можно сделать с помощью метода fillna, принимающего значение, на которое будут заменены пропущенные значения.
data = pd.read_csv('https://stepik.org/media/attachments/taxi_peru.csv', sep=';')
data.loc[:, ['driver_id','taxi_id']].head(3)
data.loc[:, ['driver_id','taxi_id']].head(3).fillna(0)
До этого при отборе определённых строк мы пользовались методом query или использовали []. Вместе с тем существует метод loc (и его собрат iloc), позволяющий выбрать поднабор строк и колонок из датафрэйма. В некоторых случаях loc удобнее, но обычно запись с ним более громоздкая и он работает медленнее query.
ads_data = pd.read_csv('~/shared/homeworks/python_ds_miniprojects/6/ads_data.csv')
ads_data.head(5)
Эта запись отберёт все строки из датафрэйма, где значения в колонке platform равны 'web', и колонки от time до client_union_id
ads_data.loc[ads_data['platform'] == 'web', 'time':'client_union_id'].head()
Кусок df['platform'] == 'web' возвращает логическую серию, где напротив нужных значений стоит True. Вместо df['platform'] == 'web' может быть любое выражение, которое даст коллекцию True/False размером с число строк в датафрэйме.
Линии, что тут ещё сказать. По оси x и y откладываются значения точек, эти точки соединяются. Аргумент hue принимает имя колонки, по значениям которой идёт разделение на цвета.
sns.lineplot(x='date', y='target_audience_count', hue='event', data=ads_data)
Удобный тип графика, когда есть множество значений с 2-мя категориальными признаками (обычно это индекс и колонки в датафрэйме). По осям откладываются значения этих категориальных переменных, каждая ячейка - значение, которое мы визуализируем. Интенсивность ячейки пропорциональна значению.
df_num = pd.DataFrame(data={'col1': [5, 7, 8, 2, 9, 1], 'col2': [3, 4, 2, 7, 2, 3],
'col3': [2, 6, 7, 5, 8, 1], 'col4': [1, 9, 3, 6, 7, 2]})
sns.heatmap(df_num)
При работе с текстовыми данными часто возникает необходимость их парсить (то есть извлекать нужные данные из всего текста). Давайте представим небольшой пример, у нас есть данные о почтовых адресах пользователях, и мы хотим узнать, с каких доменов (всё, что после @) у нас пользователей больше.
vasya@yandex.ru
katya_ivanova@gmail.com
sasha@karpov.courses.com
masha@gmail.com
Мы могли бы посчитать по доменным именам value_counts, если бы они были у нас в колонке в датафрэйме. Но, что делать, если нам даны целые мэйлы?
На помощь приходят регулярные выражения. Регулярные выражения - это специальный язык для описания низкого уровня языковой грамматики. Не углубляясь в определения - ре позволяют вычленить из регулярного текста (его структура одинакова/почти одинакова на протяжении всего текста) нужные нам части
Для начала, разберём всё в простом питоне, а потом уже в пандасе. В данном примере с почтой мы можем просто воспользоваться строковые методы питона -
Но не на всех задачах встроенные методы так хорошо работают. Сначала посмотрим, как решить этот таск ре, а потом разберём что-нибудь посложнее. Итак решение, и его объяснение:
import re
mail = 'vasya@yandex.ru'
pattern = re.compile('@([\w.]+)')
pattern.findall(mail)
import re - импотиртируем библиотеку для работы с регулярными выражениями, в чистом питоне их нет
pattern = re.compile('@([\w.]+)') - с помощью функции compile из модуля re создаём паттерн (образец), который будем искать в тексте, и помещаем его в переменную pattern. Паттерн создаётся при помощи строки - о том, с чем совпадает (что матчит) этот паттерн мы поговорим дальше. Паттерн обладает набором методов (так же, как у датафрэйма есть методы), один из которых мы и используем
pattern.findall(mail) - применяем метод findall на строке с почтой. Метод findall возвращает список со всеми встречаниями паттерна (pattern) в строке, где мы ищем (mail)
В результате мы получили список с одним мэтчем - ['yandex.ru']
На первый взгляд кажется непонятно (и неудивительно - мы ещё не обсуждали как описывается паттерн) и бессмысленно, ведь есть split. Однако, у этого способа уже на чуть усложнённой задаче есть плюсы.
text = '''We have several emails - vasya@yandex.ru, katya_ivanova@gmail.com,
sasha@karpov.courses.com and also masha@gmail.com'''
pattern.findall(text)
Одним питоновским сплитом мы бы тут не отделались! В следующей главе поговорим об описании паттерна.
import re
text = 'the gray fox jumps over the lazy dog'
pattern = re.compile('ox')
pattern.findall(text)
То мы найдём все ox в тексте. С числами такая же история. Да и со многими знаками типа @.
Специальные символы для ре, обозначающие группу значений
\d - любая цифра (digits)\D - всё что угодно кроме цифры\s - любой пробельный символ (spaces)\S - всё что угодно кроме пробельного символа\w - любая буква, цифра или _ (words)\W - всё что угодно кроме буквы, цифр или _То есть нижний регистр - хотим это, верхний регистр - хотим не это Ещё есть
. - любой символПара примеров
Тройки цифр:
text = '+7-921-000-00-00 +7-981-555-55-55'
pattern = re.compile('\d\d\d')
pattern.findall(text)
Фрагменты из 4-ёх знаков, начинающиеся с в:
asimov = '''Робот не может причинить вред человеку или своим бездействием допустить, чтобы человеку был причинён вред.
Робот должен повиноваться всем приказам, которые даёт человек, кроме тех случаев, когда эти приказы противоречат Первому Закону.
Робот должен заботиться о своей безопасности в той мере, в которой это не противоречит Первому или Второму Законам.'''
pattern = re.compile('в...')
pattern.findall(asimov)
Скобочки () имеют особое значение - они обозначают группы символов в паттерне. Благодаря этому мы можем извлечь кусочки из заматчившегося паттерна. Например, достанем только код (города? оператора? в общем какой-то код) из телефонного номера:
text = '+7-921-000-00-00 +7-981-555-55-55'
pattern = re.compile('(\d\d\d)-(\d\d\d)')
pattern.findall(text)
Обратите внимание, что мы получаем кортежи, где каждый элемент - группа из одного матча. Это позволяет нам извлечь нужную группу из каждого матча (хотя бы просто циклом по pattern.findall(...) с извлечением 0-ого элемента). В то время как раньше, мы получали все тройки цифр сплошняком.
Другое наблюдение - минус в паттерне никак не отображается - мы матчим в тексте 3 цифры, минус, 3 цифры, то есть он должен быть в тексте, чтобы заматчить, но мы можем убрать его из аутпута.
Это просто ... в общем это очень круто) Квантификаторы - это символы, позволяющие специфицировать, сколько раз нужно повторить то, что идёт до них. Вот их виды:
* - сколько угодно раз (0 - бесконечность)+ - 1 или больше раз? - 0 или 1 раз (то есть или предыдущий символ будет, или нет){} - в скобочках можно указать точное время или диапазон, читайте подробнее о них и других символах в документацииКвантификаторы можно ставить после символа или группы. К примеру, отберём весь текст, начинающийся со слов человек где есть:
asimov = '''Робот не может причинить вред человеку или своим бездействием допустить, чтобы человеку был причинён вред.
Робот должен повиноваться всем приказам, которые даёт человек, кроме тех случаев, когда эти приказы противоречат Первому Закону.
Робот должен заботиться о своей безопасности в той мере, в которой это не противоречит Первому или Второму Законам.'''
pattern = re.compile('человек.*')
pattern.findall(asimov)
* и + стараются сожрать как можно больше символов в паттерн (почти как Уроборос).
Что делать, если хочется искать \d (то есть идущие друг за другом \ и d) или просто \? Заэкранировать их ещё одним \! Однако, стоит помнить, что в питоне \ тоже специальный символ, поэтому придётся добавлять ещё один \ и в результате паттерн будет выглядеть захламлённым. Чтобы этого не происходило, используйте raw строки, то есть ставьте буковку r перед строкой с паттерном.
Разумеется, это далеко не всё, но этого хватит, чтобы начать)
Регэкспы как дота просты для базового освоения, и сложны для использования на уровне мастера, но при этом бывают очень полезны в рутине. Но сразу предостерегаем вас - если есть готовая библиотека для парсинга специфичного текста, то воспользуйтесь ею (html - beautiful soap, json - json), так как регэксп это в большинстве случаев решение, которое подходит, чтобы быстро решить задачку с текстом без определённого формата или с простым форматом (регулярным).
Для строковых колонок датафрэйма есть специальный атрибут str, содержащий множество методов работы со строками (по сути там векторизованные питоновские методы для строк). Вызов самого по себе str ничего особо не даёт.
df = pd.DataFrame({'name': ['Aristotle', 'Zenon', 'Kant', 'Hume', 'Heidegger']})
df
Строковые методы из str применяются к каждой ячейке колонки. Например, проверим, начинаются ли значения колонки name на A
df['name'].str.startswith('A')
О том, что произошло - после обращения к атрибуту str мы вызвали метод startswith, в который передали строку A. Это аналогично вызову типа:
'Aristotle'.startswith('A')
Только мы делаем это со всей колонкой. И как результат получаем такую же колонку булиновских значений - True, если начинается на a, и False, если не начинается. Исходная колонка не меняется, возвращается новая.
Или, перевод всех значений в upper case:
df['name'].str.upper().head()
По сути, когда мы пишем str, то получаем доступ к строкам в колонке, и как бы вызываем от них строковый метод. Вдобавок к этому, мы можем делать срезы, возьмём первые 5 букв, к примеру:
df['name'].str[:5].head()
Что аналогично:
'Aristotle'[:5]
По аналогии со сплитом обычной строки, мы можем засплиттить строки в серии, и получить на каждую ячейку по списку. Наш датафрэйм немного изменился:
df = pd.DataFrame({'info': ['Aristotle, (384 BC-322BC)',
'Zeno of Elea, (c. 495 BC-c. 430 BC)',
'Immanuel Kant, (1724-1804)',
'David Hume, (1711-1776)',
'Martin Heidegger, (1889-1976)']})
df
df['info'].str.split(',')
Здесь мы получили списки с двумя элементами.
При работе с такими колонками, по спискам можно индексироваться и слайситься так же при помощи атрибута str
df['info'].str.split(',').str[0]
Что при работе с одним списком аналогично:
['David Hume', ' (1711–1776)'][0]
Чтобы извлечь данные из строк в пандасе есть специальный метод - extract. Он принимает паттерн ре, позволяющий выдрать нужные куски из текста в отдельные колонки.
df['info']
Извлечём отсюда информацию об имени и даты жизни
df['info'].str.extract('(?P<name>\w+), \((?P<data>.+)\)')
Итак
df['info'].str - обращаемся к атрибуту со строковыми методамиextract - вызываем метод, выдирающий части текста(?P<name>\w+) - это именованная группа, она как группа, только к ней можно обращаться по имени (?P...) - говорит питону, что это именованная группа<name> - имя группы, в данном случае name\w+ - матчим буквы/цифры/подчёркивания, которые встречают 1 или больше раз подряд, \( - запятая, пробел и скобочка, которые идут после первой группы (\ потому что символ скобки имеет специальное значение в ре)(?P<data>.+) - другая именованная группа?P - опять же, это идентификатор группы<data> - имя группы data.+ - берём любой символ 1 или больше раз подряд\) - скобочка после 2-ой группыНайдя в ячейке текст, подходящий под такое описание, extract выдерет его, разобьёт на указанные группы, и поместит в новые колонки с именами как в указанных группах. Данный паттерн не самый оптимальный, но не использует новых метасимволов.
extract возвращает новый датафрэйм с экстрагированным текстом.
В пандасе есть удобный метод отбора колонок или строк по их названию - filter. Помимо строк он так же может работать с регэкспами, что позволяет гибко отбирать колонки.
pd.DataFrame.filter(items/like/regex, axis)
items - принимает список с названиями колонок или строк, особой разницы по сравнению с loc'ом нетlike - принимает строку, и возвращает все колонки, где в названии содержится строка, переданная в likeregex - принимает строку, означающую паттерн ре, возвращает все колонки с названиями, матчимящимися на паттернaxis - параметр для обозначения того, отбираем мы колонки или строки, принимает 'columns' или 'index', по умолчанию фильтрует колонкиПосмотрим на примере данных о перевозках
Отберём все колонки с id в названии:
ads_data.filter(like='id').head()
Как видите, мы получили только колонки с id в названии - все колонки с идентификаторами и has_video
А теперь возьмём по паттерну только колонки с идентификаторами:
ads_data.filter(regex='_id').head()
Модуль для работы с данными (преимущественно численными), на котором основан пандас. Часть методов у этих модулей пересекается, и функции нампая хорошо работают с сериями и датафрэймами. Традиционно сокращается при импорте как np
import numpy as np
Для примера возьмём 10-ичный логарифм от серии:
import numpy as np
s = pd.Series({'b': 10, 'a': 100, 'c': 1000})
s
np.log10(s)
То есть достаточно просто передать внутрь функции объект, к элементам которого мы хотим применить функцию
Иногда (часто при работе с временными данными) нужно произвести агрегирующие вычисления, захватывающие определённый промежуток данных (не всю колонку). То есть мы будем работать в определённом "окне" значений колонки. Окна бывают разные, простой вариант - скользящее -
Для вычисления 1-ого значения берётся, допустим 4, предыдущих значения и то, которое рассчитываем. Для вычисления 2-ого берутся значения со сдвигом на 1 (то есть со 2-ого по 6-ое) и так далее.
df_num
df_num.col1.rolling(3).mean()
Скользящее окно призывается с помощью метода rolling, принимающего период окна. Эта функция похожа на группировку тем, что результат не просматривается, и необходимо применить агрегирующую функцию (в примере выше mean).
4 первых значения не были вычислены, так как для них нет 5 значений, по которым они бы вычислялись.
Если рассчёт каждого нового значения ровно по периоду не важен, можно использовать параметр min_periods, принимающий число, указывающее сколько минимум должно быть значений, чтобы посчитать результат.
df_num.col1.rolling(3, min_periods=1).mean()
Посмотрим на скользящее среднее более подробно. Периодически требуется не только преобразовать данные, но и сгладить сам ряд. Сглаживание временных рядов позволяет избавиться от шума в данных и более точно увидеть линию общего тренда. Один из способов — использование скользящего среднего. Что же это такое?
В pandas есть уже готовая реализация этого метода — pd.rolling(). Функция принимает несколько параметров, первый и самый важный из которых window — размер окна, также называемый шириной окна. Данный параметр отвечает за число наблюдений, которые используются для подсчета скользящего среднего. К примеру,
где n — размер окна, t — момент времени.
Предположим, имеется 5 наблюдений:
df = pd.DataFrame({'value': [0, 1, 2, 3, 4]})
df
Для подсчета скользящего среднего с размером окна 2 вычисления будут выглядеть следующим образом:
$$SMA_{t} = \frac{x_t - x_{t-1}}{2}$$где $x_t$ — значение в текущий момент времени, $x_{t-1}$ — в предыдущий.
df.rolling(window=2).mean()
Обратите внимание, что теперь на месте самого первого значения стоит NaN, поскольку указанный размер окна 2 подразумевает наличие двух значений для подсчета, и вполне логично, что скользящее среднее для первого наблюдения вычислить не получится, поскольку других значений перед ним нет. Если увеличить размер окна до 3, то NaN будет стоять и вместо второго наблюдения, и так далее.
Еще один вариант использования pd.rolling() — центрированное скользящее среднее. В таком случае используются наблюдения до, после и во время t. В pandas за это отвечает параметр center, который по умолчанию равен False.
Для подсчета центрированного среднего с размером окна 3:
$$SMA_{t} = \frac{x_{t-1} + x_t + x_{t+1}}{3}$$где $x_t$ — значение в текущий момент времени, $x_{t-1}$ — в предыдущий, $x_{t+1}$ — последующий.
df.rolling(window=3, center=True).mean()
# (0+1+2)/3=1
# (1+2+3)/3=2
Следующий шаг — экспоненциальное сглаживание. В предыдущем подходе (скользящее среднее), использовались nnn последних наблюдений и все они имели равный вес. В данном случае веса экспоненциально уменьшаются, т.е. более "старые" события имеют меньший вес, а для подсчета используются все имеющиеся наблюдения.
$$EMA_t = \alpha * P_t + (1 - \alpha)*EMA_{t-1}$$где:
Более подробно про расчет EMA можно почитать здесь.
Для подсчета экспоненциального скользящего среднего в pandas есть метод ewm(). Например,
df
df.ewm(span=2).mean()
Время от времени вам будет нужно пройтись по нескольким спискам одновременно - то есть получать элементы с одним индексом из каждого из них. Можно сделать это так:
nums = [3, 5, 7]
letters = ['all', 'for', 'exoplanets colonization']
for i in range(len(nums)):
num = nums[i]
letter = letters[i]
# do something with them
Но есть более удобный способ - функция zip()! По сути она позволяет перебирать элементы списков одновременно, возвращая на каждой итерации кортеж с элементами из одинаковой позиции соответствующих списков.
nums = [3, 5, 7]
letters = ['all', 'for', 'exoplanets colonization']
for i in zip(nums, letters):
num = i[0]
letter = i[1]
# do something with them
Кажется, что лучше не стало, и в такой записи, и вправду стало ненамного лучше. Но мы можем сразу извлечь num и letter из кортежа с помощью записи.
nums = [3, 5, 7]
letters = ['all', 'for', 'exoplanets colonization']
for num, letter in zip(nums, letters):
# do something with them
Связано это с тем, что в питоне можно распаковывать кортежи и списки в переменные, например.
num, letter = (3, 'all') # num == 3 and letter == 'all'
Здесь мы присваиваем элементы из кортежа (3, 'all') в соответствующие переменные.
Многие настройки рисования можно установить 1 раз для скрипта (обычно в его начале), избавившись таким образом от повторов.
# Font size will be increased, background of figures will be white, grid will be present and size of plots will be increased
sns.set(
font_scale=2,
style="whitegrid",
rc={'figure.figsize':(20,7)}
)
Хорошие графики обладают подписанными осями, заголовком и начинаются от 0 при сравнении между собой нескольких значений (например, на барплоте)!
Есть несколько способов провести кастомизацию графика.
При создании графика возвращается объект, который хранит о нём информацию. Через него можно задать параметры кастомизации.
ax = df.plot() # create plot
ax.set_xlabel('X axis name') # Label of x axis
ax.set_ylabel('Y axis name') # Label of y axis
ax.set_title('Plot title') # Title of the plot
y_labels = [str(int(i * 100)) + '%' for i in ax.get_yticks()] # Prepare custom labels of axis values
ax.set_yticklabels(y_labels) # Set new labels
ax.set_xticklabels(labels=df.index, rotation=90) # Same just for x axis and totate labels
#to perpendicular configuration
sns.despine() # Get rid of axis on the plot
import matplotlib.pyplot as plt
ax = df.plot()
plt.xlabel('X axis name')
plt.ylabel('Y axis name')
plt.title('Plot title')
y_labels = [str(int(i * 100)) + '%' for i in ax.get_yticks()]
ax.set_yticklabels(y_labels)
ax.set_xticklabels(labels=df.index, rotation=90)
sns.despine()
Давайте подробнее посмотрим на форматирование графиков, а именно на их расположени, подписи осей, рамки и сетки Как построить несколько графиков рядом друг с другом?
Для этого можно использовать plt.subplots(). Благодаря этому методу мы разобьём общее полотно графика на части и получим матрицу, где в каждой ячейке можно размесить свой график. Функция принимает несколько параметров:
nrows – число рядов/строк, на которые мы разделим полотноncols – число колонок, на которые мы разделим полотноfigsize – размер картинки, общего полотнаsharey – будет ли ось y общей для графиков (пошаренной) и между какими (можно задать общую ось между всеми графиками, между графиками одной строки или одного столбца)sharex – то же самое для оси ximport matplotlib.dates as mdates
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(20, 10), sharey='col', sharex=True)
Мы указали 2 строки и 2 колонки для графиков (то есть у нас будет матрица 2 на 2, где можно уместить до 4-ёх рисунков). Размер картинки указывается как кортеж с шириной и высотой графика 'col' в sharey указывает на то, что у графиков в каждой колонке будет одинаковая ось y True в sharex означает, что ось x будет одинаковая у всех графиков
В результате возвращается 2 объекта - сама картинка и оси каждого из графиков (plt довольно навороченная библиотека в плане организации графика), которые мы сразу записываем в переменные fig и axes.
В последнем находится массив из объектов axes:
np.array([['<matplotlib.axes._subplots.AxesSubplot object at 0x11d35dd50>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e71df10>'],
['<matplotlib.axes._subplots.AxesSubplot object at 0x11e783410>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e7bc310>']],
dtype=object)
Это по сути numpy эррей 2 на 2 - то есть матрица по-человечески. В каждой её ячейке можно нарисовать график независимый от других ячеек (или зависимый по осям, если их зашарить).
Для отрисовки графиков используем цикл. Сначала соединяем с помощью zip():
windowaxesavocado_mean = pd.read_csv('https://stepik.org/media/attachments/avocado_mean.csv')
avocado_mean.head()
windows = [2, 20, 30, 40]
colors = ['coral', 'blue', 'green', 'purple']
for window, ax, color in zip(windows, axes.flatten(), colors):
ax.plot(avocado_mean.rolling(window=window).mean(), label=window, color=color)
Здесь мы используем метод .flatten() у axes, чтобы перевести матрицу в одномерный массив и просто по нему проитерироваться. Содержимое axes.flatten():
np.array(['<matplotlib.axes._subplots.AxesSubplot object at 0x11d35dd50>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e71df10>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e783410>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e7bc310>'],
dtype=object)
Теперь для каждого графика:
for ax in axes.flatten():
# удаляем рамку
ax.set_frame_on(False)
# устанавливаем major locator – 4 января для каждого года
ax.xaxis.set_major_locator(mdates.MonthLocator(bymonth=1, bymonthday=4))
# показывать в формате сокращенного названия месяца и дня (Jan 04)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d'))
# под major locator - minor locator, т.е. редактируем minor ticks
ax.xaxis.set_minor_locator(mdates.YearLocator(month=1, day=1))
# показываем год
ax.xaxis.set_minor_formatter(mdates.DateFormatter('\n%Y'))
# делаем сетку графика совсем немного серой и наполовину прозрачной
ax.grid(True, color='#e2e2e2', alpha=0.5)
В результате мы определим где будут находиться лэйблы тиков и как они будут выглядеть.
Для каждого рисунка устанавливаем название с помощью ax.set() и форматирования строк. Также используем ax.tick_params() для изменения внешнего вида ticks.
for name, ax in zip(['1','2','3','4'], axes.flatten()):
ax.set(title='График {}'.format(name))
ax.tick_params(labelbottom=True, which='both')
Здесь мы проитерировались по нашим графикам, задали заголовок каждого (title), а также указали, что хотим нарисовать названия у тиков снизу.
Объединяем кусочки кода в одну ячейку, plt.show() и готово!
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(20, 10), sharey='col', sharex=True)
windows = [2, 20, 30, 40]
colors = ['coral', 'blue', 'green', 'purple']
for window, ax, color in zip(windows, axes.flatten(), colors):
ax.plot(avocado_mean.rolling(window=window).mean(), label=window, color=color)
np.array(['<matplotlib.axes._subplots.AxesSubplot object at 0x11d35dd50>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e71df10>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e783410>',
'<matplotlib.axes._subplots.AxesSubplot object at 0x11e7bc310>'],
dtype=object)
for ax in axes.flatten():
# удаляем рамку
ax.set_frame_on(False)
# устанавливаем major locator – 4 января для каждого года
ax.xaxis.set_major_locator(mdates.MonthLocator(bymonth=1, bymonthday=4))
# показывать в формате сокращенного названия месяца и дня (Jan 04)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d'))
# под major locator - minor locator, т.е. редактируем minor ticks
ax.xaxis.set_minor_locator(mdates.YearLocator(month=1, day=1))
# показываем год
ax.xaxis.set_minor_formatter(mdates.DateFormatter('\n%Y'))
# делаем сетку графика совсем немного серой и наполовину прозрачной
ax.grid(True, color='#e2e2e2', alpha=0.5)
for name, ax in zip(['1','2','3','4'], axes.flatten()):
ax.set(title='График {}'.format(name))
ax.tick_params(labelbottom=True, which='both')
plt.show()
Если какой-то из аргументов вашей функции часто имеет одно значение, имеет смысл прописать его по умолчанию. Для этого поставьте после имени аргумента = и укажите значение, которое будет принимать аргумент, если он не получит его при вызове функции. Аргументы по умолчанию должны идти после обычных.
def greetings(name='guys'):
print('Hi,', name)
greetings('Bob')
greetings()
Если указать в параметре on функции pd.merge список из колонок, то произойдёт объединение по их комбинации. То есть будут объединены строки, где совпадают значения во всех указанных в on колонках.
# Merge by combination of Company Id and Company Name
orders_with_sales_team = pd.merge(order_leads, sales_team, on=['Company Id','Company Name'])
Если вам нужно перейти от чисел к категориям, воспользуйтесь функцией pd.cut. Она принимает массив значений и число интервалов/список из границ интервалов.
values = pd.Series([1, 2, 3, 4, 5, 6])
values
pd.cut(values, 3)
pd.cut(values.head(),[0, 3, 5, 10])
Как видите, числа заменились на интервалы.
Для добавления своих названий используется аргумент labels, куда подаётся список из названий интервалов.
pd.cut(values,
bins=[0, 3, 5, 10],
labels=['low', 'medium', 'high'])
Графики, отражающие распределение значений:
df = pd.read_csv('https://stepik.org/media/attachments/2_taxi_nyc.csv')
df.head()
sns.distplot(df.pickups, kde=False)
Kernel Density Estimation - аргумент добавляет аппроксимацию распределения, по умолчанию True. При этом распределение шкалируется и становится графиком probability density function.
sns.distplot(df.pickups)
Библиотека для создания интерактивных графиков. Например:
import plotly.express as px
px.line(df, df.pickup_dt[:1000], df.pickups[:1000])
Немного теории: модульность - важное свойство программ, которое обеспечивается языком программирования. Мы можем разбить код программы по нескольким обособленным по смыслу файлам. Это значительно облегчает разработку и тестирование сколько-нибудь сложных приложений.
Небольшой пример - вы написали с 10-ок функций, решающих ваши задачи. Держать их в одном файле и там же вызывать так себе идея, потому что получается бардак. Хорошим шагом будет разделение функций от скриптов, где вы непосредственно применяете их.
Каждый питоновский скрипт (с расширением py, не юпитер ноутбук) является модулем. Чтобы получить доступ к его содержимому необходимо произвести импорт. При импортировании файла он целиком исполняется (как если бы вы выполнили его в юпитер ноутбуке).
Для получения своего модуля:
Чтобы модуль можно было импортировать, он должен быть виден питону. Он смотрит модули в нескольких местах.
Самый простой вариант - держать ваш файл-модуль в той же папке где работаете.
Помимо импорта видов:
import pandas as pd
и
import os
Существуют другие варианты:
from my_module import my_function - импортировать функцию my_function, содержащуюся в my_module.pymy_functionfrom my_module import *my_function так же можно использовать сразу в коде по её имени. Все остальные переменные/функции тоже были выгружены из модуля. Это не рекомендуемая практика при импорте большинства модулей, так как захламляется именами из модуля скрипт (меньше свободных имён для ваших переменных). Но вполне валидно для модулей с небольшим числом имён.Функция np.where() позволяет задать новые значения, основываясь на старых. Она принимает 3 аргумента -
condition – условие, то есть серия со списком True и Falsex – на что заменить Truey – на что заменить Falsea = pd.Series([0, 1, 2, 3, 1, 2, 3, 4, 5, 6])
np.where(a > 2, 'Higher than 2', 'Lesser than 2')
notna – это метод-антоним isna, возвращает True, если значение не NA. Альтернативный способ получить такой результат – инвертировать результат применения isna с помощью ~ (так во многих языках обозначают not, в логических сериях пандаса так же).
df.notna()
# Same as previous
~df.isna()
Помните ошибки, которые возникают при работе в питоне? Хорошая новость – их можно обрабатывать, но, естественно, нужно делать это разумно. Что имеется в виду: мы можем сделать так, чтобы программа продолжила работать дальше после ошибки. Это делается с помощью конструкции try-except:
expenditures = 0
income = 100
try:
ratio = income / expenditures
except:
print('Something went wrong, mb expenditures are 0?')
try и except должны быть вместе вплотную. Как это работает: после try ставится : и идёт блок кода, который пытается выполниться. Если ему это удаётся, то блок except (тоже с :) пропускается. Если же в блоке try произошла ошибка, то вместо прекращения ошибки идёт переход в блок except и выполняется код, содержащийся там. Далее следует выход из except и программа работает с кодом в скрипте дальше.
Теперь о том, зачем это нужно. Не нужно вставлять try, чтобы ваш код не падал с ошибками, и радоваться. Это специальный инструмент для работы с чувствительными местами, где может произойти ошибка, и где вам нужно действовать разными способами в случаях успешной работы блока или ошибки.
Во многих случаях try-except можно заменить полотном предварительных проверок, то есть проверками выполнимости перед выполнением рискованной операции (в примере выше – проверкой на равенство expenditures 0).
Мы уже много работали с логическими сериями, например:
bool_ex = pd.Series([True, False, True, True, False],
index = ['company1', 'company2', 'company3', 'company4', 'company5'])
bool_ex
Иногда необходимо выяснить агрегированное значение серии – все ли там значения True, или есть ли хотя бы один True среди них. Для этого используются специальные методы.
Все ли значения в серии True? Всё равно что поставить and между всеми значениями.
bool_ex.all()
Есть ли в серии хотя бы одно значение True? Всё равно что поставить or между всеми значениями.
bool_ex.any()
Служит для генерации случайных чисел. Есть аналог в numpy.
import random
# returns number from [a, b]
random.randint(1, 6)
Значительно облегчающая выполнение задач вещь. По сути библиотека от создателей веб-сервиса (сайта, где можно что-то сделать), позволяющая быстро выполнить действия с этим сервисом. Как правило для работы с api необходимо получить токен.
Токен – это уникальная последовательность символов, позволяющая авторизоваться на сайте и работать с API. Пример токена: d9b70b356593da15f73083d7a0e0554586ca5f743fc0f30dabb993f9917b4317725d4db40a3d5e3729607
API для ВКонтакте, позволяет программно выполнять действия, например, писать сообщения, выбирать друзей и так далее.
Подготовка автоматизации:
Если вам не помогли предыдущие пункты, то вот альтернативный способ:
import vk_api
# Token which you obtained via vk
app_token = 'token`
# id of the 1st chat
chat_id = 1
# id of my user-receiver
my_id = 148915653
# Initialize session
vk_session = vk_api.VkApi(token=app_token)
# Make it possible to use vk api methods as python methods
vk = vk_session.get_api()
#### Отправка сообщений
vk.messages.send(
chat_id=chat_id,
random_id=random.randint(1, 2 ** 31),
message='Это я, Почтальон Печкин!')
#### Отправка документов
# Specify path to the file and its future name in the message
path_to_file = '/home/arleg/Downloads/Telegram Desktop/corr_plot.pdf'
file_name = 'plot.pdf'
upload_url = vk.docs.getMessagesUploadServer(peer_id=my_id)["upload_url"]
file = {'file': (file_name, open(path_to_file, 'rb'))}
# Send request to post this doc on vk.com
response = requests.post(upload_url, files=file)
json_data = json.loads(response.text)
saved_file = vk.docs.save(file=json_data['file'], title=file_name)
attachment = 'doc{}_{}'.format(saved_file['doc']['owner_id'], saved_file['doc']['id'])
vk.messages.send(
chat_id=chat_id, # id of chat where to send
random_id=random.randint(1, 2 ** 31), # random number for message identification
message='Привёз посылку для вашего мальчика!', # message text, optional here
attachment=attachment) # attachment name
Заменив chat_id=chat_id на user_id=my_id, можно отправлять сообщения себе. Только поставьте в my_id свой id)

Нажмите New project

Введите имя и нажмите Create



Нажмите Enable для каждого апи (нужно для их подключения)

Для выбора следующего API выберите Google APIs слева, а затем перейти в Library







import gspread
from oauth2client.service_account import ServiceAccountCredentials`
# Specify path to your file with credentials
path_to_credential = 'credentials.json'
# Specify name of table in google sheets
table_name = 'name of your table'
scope = ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name(path_to_credential, scope)
gs = gspread.authorize(credentials)
work_sheet = gs.open(table_name)
# Select 1st sheet
sheet1 = work_sheet.sheet1
# Get data in python lists format
data = sheet1.get_all_values()
# Get header from data
headers = data.pop(0)
# Create df
df = pd.DataFrame(data, columns=headers)
df.head()
После этого в df у вас должно быть содержимое таблицы
Инструкция на английском и ещё одна
import pandas as pd
import gspread
from df2gspread import df2gspread as d2g
from oauth2client.service_account import ServiceAccountCredentials
scope = ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive']
my_mail = 'your@mail'
path_to_credentials = 'crdentials.json'
# Authorization
credentials = ServiceAccountCredentials.from_json_keyfile_name(path_to_credentials, scope)
gs = gspread.authorize(credentials)
#### Загрузка таблицы из гугл доков
# Name of the table in google sheets,
# can be url for open_by_url
# or id (key) part for open_by_key
table_name = 'table name' # Your table
# Get this table
work_sheet = gs.open(table_name)
# Select 1st sheet
sheet1 = work_sheet.sheet1
# Get data in python lists format
data = sheet1.get_all_values()
# Get header from data
headers = data.pop(0)
# Create df
df = pd.DataFrame(data, columns=headers)
df.head()
#### Создание своей таблицы
# Create empty table
table_name = 'A new spreadsheet'
sheet = gs.create(table_name)
# Make it visible to other guys
sheet.share(my_mail, perm_type='user', role='writer')
Для переноса датафрэйма в таблицу гугл дока, нужно, чтобы вы сделали эту таблицу из питона. Поэтому прогоните перед этой частью предыдущий раздел с желаемым названием таблицы.
# Create your df
df = ...
# Looks like spreadsheet should be already present at the dist (so, run code in create table section)
sheet = 'Master'
d2g.upload(df, table_name, sheet, credentials=credentials, row_names=True)
При этом необходимо, чтобы в df не было повторяющихся индексов (reset_index)
Библиотека для работы с json'ом – одним из самых распространённых форматов данных в вэбе. Он используется для пересылки данных между веб-сервисами, и очень похож на словари в python. Одноимённая библиотека json используется для его преобразования в действительно питоновский словарь.
import json
# Convert json to python dict
json_data = json.loads(some_json)
Библиотека requests позволяет взаимодействовать с сайтами. Метод get() принимает ссылку на сайт в виде строки, и возвращает объект, содержащий ответ с сайта в виде строки.
import requests
query = requests.get(url)
Если вам не нравится вручную делать ссылки-запросы для Яндекс.Метрики, есть решение:
from urllib.parse import urlencode
# Base path to service
base_url = 'https://api-metrika.yandex.net/stat/v1/data?'
# Parameters of query
params = {'metrics': 'ym:s:visits',
'dimensions': 'ym:s:date',
'id': '44147844'}
visits_url = base_url + urlencode(params)
visits_url
Получившаяся ссылка в нескольких местах не совпадает с полученной ранее, но она такая же – просто в ней произведено дополнительное экранирование символов. Далее её можно точно так же использовать в requests.get
Чтобы закодировать несколько значений параметра с одним именем, просто передайте в словарик вместо значения к нужному параметру список из них, и укажите параметр doseq=True
# Base path to service
base_url = 'https://api-metrika.yandex.net/stat/v1/data?'
# Parameters of query
params = {'metrics': 'ym:s:visits',
'dimensions': ['ym:s:date', 'ym:s:isRobot'],
'id': '44147844'}
visits_url = base_url + urlencode(params, doseq=True)
visits_url
Параметры requests.get
На самом деле всё можно сделать кратче - достаточно запихнуть словарь с параметрами в params внутри requests.get
requests.get(base_url,
params={
'metrics': 'ym:s:visits',
'dimensions': ['ym:s:date', 'ym:s:isRobot'],
'id': 44147844
})
Этот код делает то же самое, что предварительное конструирование ссылки и requests.get на ней
Список доступных параметров для запросов в Яндекс.Метрике
import pandas as pd
import requests
import json
# Base url to service
url = 'https://api-metrika.yandex.net/stat/v1/data?'
# &-separated parameters of query in a form of name=value, taken from the metrica site
visits = 'metrics=ym:s:visits&dimensions=ym:s:date&id=44147844'
url = url + visits
# Get json of response
query = requests.get(url)
json_data = json.loads(query.text)
# Conversion of obtained json to dataframe
visits_data = pd.DataFrame([(
i['dimensions'][0]['name'],
i['metrics'][0]) for i in json_data['data']],
columns=['date', 'visits'])
Маленькое напоминание: Telegram забанен, поэтому для открытия ссылок, связанных с ним, необходим VPN.
Чтобы автоматизировать работу в телеграме необходимо создать бота и получить токен. Для этого:
Перечень команд доступных для общения с Отцом всех ботов используйте VPN, если ссылка не открывается).
Воспользуйтесь username бота (или ссылкой на него), полученным от Botfather, начните диалог и отправьте ему что-нибудь. Затем введите в браузере ссылку вида:
https://api.telegram.org/bot<token>/getUpdates
Где вместо <token> будет ваш токен.
В открывшемся окне вы увидите содержание json файла, где будет содержаться id чата (result > 0 > chat > id) Сохраните его.
После этого через бота можно посылать сообщения вам с помощью модуля request. Чтобы отправлять сообщения кому-нибудь другому, попросите его начать диалог с ботом, и повторите операцию с просмотром страницы, чтобы выяснить id чата.
Для обхода блокировки в коде используются прокси, помните, что их работа, к сожалению, нестабильна – может быть долгий отклик и программа будет долго выполняться, или они могут отрубать доступ из-за большого числа запросов. В таких случаях нужно брать другое прокси. Для Telegram нужны https прокси.
import requests
import json
from urllib.parse import urlencode
token = 'your_token'
chat_id = 123 # your chat id
message = 'test' # text which you want to send
params = {'chat_id': chat_id, 'text': message}
base_url = f'https://api.telegram.org/bot{token}/'
url = base_url + 'sendMessage?' + urlencode(params)
proxy = {'https': 'https://ip:port'}
resp = requests.get(url, proxies=proxy)
После этого бот должен прислать вам сообщение test
# Path to necessary file
filepath = 'your_path'
url = base_url + 'sendDocument?' + urlencode(params)
files = {'document': open(filepath, 'rb')}
resp = requests.get(url, files=files, proxies={'https': 'https://ip:port'})
В результате ваш документ отправится в чат.
Существуют библиотеки для создания ботов в телеге. Лучше пользоваться ими, но будьте осторожны, может слететь юпитер (старых версий, сейчас скорее всего всё будет норм).